Imports System.Drawing
Imports System.Windows.Forms
Namespace UI.Windows
  ''' <summary>
  ''' Type of date to autoload.
  ''' </summary>
  ''' <remarks></remarks>
  Public Enum ComboBoxListType
    ''' <summary>
    ''' Do not autoload date.
    ''' </summary>
    ''' <remarks></remarks>
    None
    ''' <summary>
    ''' Autoload US States.
    ''' </summary>
    ''' <remarks></remarks>
    UsStates
    ''' <summary>
    ''' Autoload Countries.
    ''' </summary>
    ''' <remarks></remarks>
    Countries
  End Enum

  ''' <summary>
  ''' Modified ComboBox control.
  ''' </summary>
  ''' <remarks></remarks>
  Public Class ComboBox
    Inherits System.Windows.Forms.ComboBox
    Private initialDropDownWidth As Integer = 0
    Private _preLoadData As ComboBoxListType = ComboBoxListType.None

    ''' <summary>
    ''' Autoloads a data list.
    ''' </summary>
    ''' <value><see cref="ComboBoxListType">List</see> to autoload.</value>
    ''' <returns><see cref="ComboBoxListType">List</see> to autoload.</returns>
    ''' <remarks></remarks>
    <System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Always), System.ComponentModel.Browsable(False)> _
        Public Property AutoLoadDataList() As ComboBoxListType
      Get
        Return _preLoadData
      End Get
      Set(ByVal value As ComboBoxListType)
        _preLoadData = value
        Me.LoadData()
      End Set
    End Property

    Private Sub UpdateDropDownWidth()
      'Create a GDI+ drawing surface to measure string widths 
      Dim ds As System.Drawing.Graphics = Me.CreateGraphics()

      'Float to hold largest single item width 
      Dim maxWidth As Single = 0

      'Iterate over each item, measuring the maximum width 
      'of the DisplayMember strings 
      For Each item As Object In Me.Items
        maxWidth = System.Math.Max(maxWidth, ds.MeasureString(item.ToString(), Me.Font).Width)
      Next

      'Add a buffer for some white space 
      'around the text 
      maxWidth += 30

      'round maxWidth and cast to an int 
      Dim newWidth As Integer = CInt([Decimal].Round(CDec(maxWidth), 0))

      'If the width is bigger than the screen, ensure 
      'we stay within the bounds of the screen 
      If newWidth > Screen.GetWorkingArea(Me).Width Then
        newWidth = Screen.GetWorkingArea(Me).Width
      End If

      'Only change the default width if it's smaller 
      'than the newly calculated width 
      If newWidth > initialDropDownWidth Then
        Me.DropDownWidth = newWidth
      End If

      'Clean up the drawing surface 
      ds.Dispose()
    End Sub

    Private Sub BetterComboBox_HandleCreated(ByVal sender As Object, ByVal e As EventArgs)
      UpdateDropDownWidth()
    End Sub
    <System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, Flags:=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)> _
    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)


      If (m.Msg = dotNetTips.Utility.UnsafeNativeMethods.WM_CTLCOLORLISTBOX) Then

        ' Make sure we are inbounds of the screen 
        Dim left As Integer = Me.PointToScreen(New Point(0, 0)).X

        'Only do this if the dropdown is going off right edge of screen 
        If Me.DropDownWidth > Screen.PrimaryScreen.WorkingArea.Width - left Then
          ' Get the current combo position and size 
          Dim comboRect As Rectangle = Me.RectangleToScreen(Me.ClientRectangle)

          Dim dropHeight As Integer = 0
          Dim topOfDropDown As Integer = 0
          Dim leftOfDropDown As Integer = 0

          'Calculate dropped list height 
          Dim i As Integer = 0
          While (i < Me.Items.Count AndAlso i < Me.MaxDropDownItems)
            dropHeight += Me.ItemHeight
            i += 1
          End While

          'Set top position of the dropped list if 
          'it goes off the bottom of the screen 
          If dropHeight > Screen.PrimaryScreen.WorkingArea.Height - Me.PointToScreen(New Point(0, 0)).Y Then
            topOfDropDown = comboRect.Top - dropHeight - 2
          Else
            topOfDropDown = comboRect.Bottom
          End If

          'Calculate shifted left position 
          leftOfDropDown = comboRect.Left - (Me.DropDownWidth - (Screen.PrimaryScreen.WorkingArea.Width - left))

          ' Postioning/sizing the drop-down 
          'SetWindowPos(HWND hWnd, 
          ' HWND hWndInsertAfter, 
          ' int X, 
          ' int Y, 
          ' int cx, 
          ' int cy, 
          ' UINT uFlags); 
          'when using the SWP_NOSIZE flag, cx and cy params are ignored 
          UnsafeNativeMethods.SetWindowPos(m.LParam, IntPtr.Zero, leftOfDropDown, topOfDropDown, 0, 0, UnsafeNativeMethods.SWP_NOSIZE)
        End If

      End If

      MyBase.WndProc(m)
    End Sub

    ''' <summary>
    ''' Constructor
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()

      initialDropDownWidth = Me.DropDownWidth

      AddHandler Me.HandleCreated, AddressOf BetterComboBox_HandleCreated

      Me.LoadData()
    End Sub

    Private Sub LoadData()

      Me.DataSource = Nothing

      Me.Items.Clear()

      Select Case Me.AutoLoadDataList

        Case Is = ComboBoxListType.UsStates
          Me.DisplayMember = "Name"
          Me.ValueMember = "Code"
          Me.DataSource = New Data.UsStateCollection

        Case Is = ComboBoxListType.Countries
          Me.DisplayMember = "Name"
          Me.ValueMember = "Code"
          Me.DataSource = New Data.CountryCollection

      End Select

    End Sub

  End Class
End Namespace